using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;

namespace Elephant.Framework
{
    public class ComponentManager : EntityCollection
    {
        List<IUpdateable> updateables = new List<IUpdateable>();
        List<IDrawable> drawables = new List<IDrawable>();

        Dictionary<EntityComponent, bool> updateDependencies = new Dictionary<EntityComponent, bool>(); // component:update dependencies?
        List<EntityComponent> componentsPendingRemoval = new List<EntityComponent>();

        bool initalized;

        public void Initialize()
        {
            foreach (Entity entity in this) {
                foreach (EntityComponent component in entity.FindAll<EntityComponent>()) {
                    component.Start();
                }
            }

            initalized = true;
        }

        public void LoadGraphicsContent(bool loadAllContent)
        {
            for (int i = 0; i < drawables.Count; i++) {
                if (drawables[i] is IDrawableContent) {
                    IDrawableContent drawableContent = drawables[i] as IDrawableContent;
                    drawableContent.LoadGraphicsContent(loadAllContent);
                }
            }
        }

        public void UnloadGraphicsContent(bool unloadAllContent)
        {
            for (int i = 0; i < drawables.Count; i++) {
                if (drawables[i] is IDrawableContent) {
                    IDrawableContent drawableContent = drawables[i] as IDrawableContent;
                    drawableContent.UnloadGraphicsContent(unloadAllContent);
                }
            }
        }

        public void Update(GameTime gameTime)
        {
            // we might want to move this to the end of Update instead, to force removals to happen in the same frame, but still delayed
            // rather than waiting for the next frame then delete
            for (int i = 0; i < componentsPendingRemoval.Count; i++) {
                if (componentsPendingRemoval[i].Owner.Contains(componentsPendingRemoval[i])) {
                    // it will only be contained if it was not placed in the list due to a Remove call 
                    // (ie. RemoveImmediate would have removed it prior to queuing it up)
                    //componentsPendingRemoval[i].Owner.RemoveImmediate(componentsPendingRemoval[i], true);

                    // experimental:
                    // update: seems to work flawlessly
                    bool updateDeps = true;
                    if (updateDependencies.ContainsKey(componentsPendingRemoval[i])) {
                        updateDeps = updateDependencies[componentsPendingRemoval[i]];
                    }

                    componentsPendingRemoval[i].Owner.RemoveImmediate(componentsPendingRemoval[i], updateDeps);

                    if (updateDependencies.ContainsKey(componentsPendingRemoval[i])) {
                        updateDependencies.Remove(componentsPendingRemoval[i]);
                    }
                }

                if (componentsPendingRemoval[i] is IDrawable) {
                    IDrawable drawable = componentsPendingRemoval[i] as IDrawable;

                    if (drawables.Contains(drawable)) {
                        drawables.Remove(drawable);
                    }
                }

                if (componentsPendingRemoval[i] is IUpdateable) {
                    IUpdateable updateable = componentsPendingRemoval[i] as IUpdateable;

                    if (updateables.Contains(updateable)) {
                        updateables.Remove(updateable);
                    }
                }

                componentsPendingRemoval[i].Dispose();
                componentsPendingRemoval.RemoveAt(i);
            }

            for (int i = 0; i < updateables.Count; i++) {
                if (updateables[i].Enabled) {
                    updateables[i].Update(gameTime);
                }
            }
        }

        public void Draw(GameTime gameTime)
        {
            for (int i = 0; i < drawables.Count; i++) {
                if (drawables[i].Visible) {
                    drawables[i].Draw(gameTime);
                }
            }
        }

        public override bool Remove(Entity item)
        {
            /* this is the common implementation, uncommented code is experimental for testing purposes (but may be the preferable solution)
            if (item != null) {
                // garbage
                foreach (IEntityComponent component in item) {
                    Remove(component);
                }
            }*/
            // this code takes use of the dictionary because when removing an entity, we will definitely not want to
            // update dependencies for each component in it - we just want everything gone. But. we don't want to
            // remove it immediately (RemoveImmediate), so the dict solves the problem where we need to know whether 
            // or not to update the dependencies once we reach removal time
            if (item != null) {
                // using FindAll instead of using the available GetEnumerator due to garbage creation when iterating over certain collections
                foreach (EntityComponent component in item.FindAll<EntityComponent>()) {
                    if (!componentsPendingRemoval.Contains(component)) {
                        componentsPendingRemoval.Add(component);
                        updateDependencies.Add(component, false);
                    }
                }
            }

            return base.Remove(item);
        }

        public override bool RemoveImmediate(Entity item)
        {
            if (item != null) {
                item.Clear();
            }

            return base.RemoveImmediate(item);
        }

        public override bool Remove(EntityComponent component)
        {
            if (!componentsPendingRemoval.Contains(component)) {
                componentsPendingRemoval.Add(component);

                return true;
            }

            return false;
        }

        public override bool RemoveImmediate(EntityComponent component, bool updateDependencies)
        {
            if (component.Owner.Contains(component)) {
                component.Owner.RemoveImmediate(component, updateDependencies);
            }

            if (component is IUpdateable) {
                IUpdateable updateable = component as IUpdateable;

                if (updateables.Contains(updateable)) {
                    updateables.Remove(updateable);
                } else {
                }
            }

            if (component is IDrawable) {
                IDrawable drawable = component as IDrawable;

                if (drawables.Contains(drawable)) {
                    drawables.Remove(drawable);
                }
            }

            component.Dispose();

            return true;
        }

        public override void Clear()
        {
            Entity[] entities = new Entity[this.Count];
            this.CopyTo(entities, 0);

            for (int i = 0; i < entities.Length; i++) {
                RemoveImmediate(entities[i]);
            }
        }

        protected override void OnEntityAdded(EntityEventArgs e)
        {
            // set manager property (outsourced, lawl!)
            // add updateables, drawables
            // hook componentadded,removed
            // remember to call Start on each component in added entity (basically the same deal as before in entitymanager at Add)

            Entity entity = e.Entity as Entity;

            entity.ComponentAdded += new EventHandler<EntityComponentEventArgs>(entity_ComponentAdded);
            entity.ComponentRemoved += new EventHandler<EntityComponentRemovedEventArgs>(entity_ComponentRemoved);

            foreach (EntityComponent component in entity) {
                Add(component);

                if (initalized) {
                    // only call start if were past the initial game startup
                    component.Start();
                }
            }

            base.OnEntityAdded(e);
        }

        protected override void OnEntityRemoved(EntityEventArgs e)
        {
            // all components should already be removed from managers lists

            base.OnEntityRemoved(e);
        }

        void entity_ComponentRemoved(object sender, EntityComponentEventArgs e)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        void entity_ComponentAdded(object sender, EntityComponentEventArgs e)
        {
            Add(e.Component);

            if (initalized) {
                // initialization has been done, component added to entity after entity construction
                // at this point we need to Start it so it can do any global communication necessary
                e.Component.Start();
            }
        }

        // this was a fix, so that when we add a component to an entity it'll also get added to the lists this manager maintains
        private void Add(IEntityComponent component)
        {
            if (component is IDrawable) {
                IDrawable drawable = component as IDrawable;
                drawable.DrawOrderChanged += new EventHandler(drawable_DrawOrderChanged);

                drawables.Add(drawable);
                drawables.Sort(DrawOrderComparer.Default);
            }

            IUpdateable updateable = component as IUpdateable;
            component.UpdateOrderChanged += new EventHandler(component_UpdateOrderChanged);

            updateables.Add(updateable);
            updateables.Sort(UpdateOrderComparer.Default);
        }

        void component_UpdateOrderChanged(object sender, EventArgs e)
        {
            updateables.Sort(UpdateOrderComparer.Default);
        }

        void drawable_DrawOrderChanged(object sender, EventArgs e)
        {
            drawables.Sort(DrawOrderComparer.Default);
        }
    }
}
